package framework.webmvc.component;

import framework.annotation.InchRequestParam;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * get the handler ready and execute it
 */
public class InchHandlerAdapter {
    //mapping of the required parameters of the handler :  [paramName,paramIndex]
    Map<String,Integer> paramMapping = new HashMap<String, Integer>();
    int numberOfHandlerParameters;
    //mapping of the parameters where http request are sending
    Map<String,String[]> reqParamMapping;

    /**
     * method that execute the handler
     * @param request HttpServletRequest
     * @param response HttpServletResponse
     * @param handler the handler need to be execute
     * @return
     */
    public InchModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        InchHandler inchHandler = (InchHandler) handler;

        //find parameter with annotation InchRequestParam
        Annotation[][] paramAnnotation = inchHandler.getMethod().getParameterAnnotations();
        numberOfHandlerParameters = paramAnnotation.length;
        //loop for each parameter
        for(int i = 0; i < numberOfHandlerParameters; i++){
            //loop for each annotation for this parameter
            for(Annotation annotation : paramAnnotation[i]){
                //check parameter have annotation or not
                if(annotation instanceof InchRequestParam) {
                    //map this parameter
                    String paramName = ((InchRequestParam) annotation).value();
                    if(!paramName.trim().equals("")){
                        paramMapping.put(paramName,i);
                    }
                }
            }
        }
        //find request and response parameter and map them
        Class<?>[] paramTypes = inchHandler.getMethod().getParameterTypes();
        for(int i =0; i < numberOfHandlerParameters; i++) {
            Class<?> type = paramTypes[i];
            if(type == HttpServletRequest.class || type == HttpServletResponse.class) {
                paramMapping.put(type.getName(),i);
            }
        }

        //the actually values that we send to the method to be invoked with
        Object[] paramValues = new Object[numberOfHandlerParameters];

        //the mapping of the params that the httpRequest send
        reqParamMapping = request.getParameterMap();
        //loop all the params sent by request and find those are needed by the handler
        for(Map.Entry<String,String[]> param : reqParamMapping.entrySet()) {
            //if this parameter is not required by this handler
            if(!paramMapping.containsKey(param.getKey())) { continue; }
            //find the index of this param in paramMapping
            int paramIndex = paramMapping.get(param.getKey());
            String[] values = param.getValue();
            //check how many values this parameter have
            if(values.length == 1) {
                paramValues[paramIndex] = typeConvertor(values[0],paramTypes[paramIndex]);
            }else {
                Object[] valuesWithCorrectType = new Object[values.length];
                //convert the values of this param into correct type
                for (int i = 0; i < values.length; i++) {
                    //typeConvertor([paramValue],[paramValueTypeClass])
                    valuesWithCorrectType[i] = typeConvertor(values[i], paramTypes[paramIndex]);
                }
                //put the right type value/values
                paramValues[paramIndex] = valuesWithCorrectType;
            }
        }

        //check if the handler need parameter of HttpServletRequest and HttpServletResponse
        if(paramMapping.containsKey(HttpServletRequest.class.getName())) {
            int index = paramMapping.get(HttpServletRequest.class.getName());
            paramValues[index] = request;
        }
        if(paramMapping.containsKey(HttpServletResponse.class.getName())) {
            int index = paramMapping.get(HttpServletResponse.class.getName());
            paramValues[index] = response;
        }

        //execute the handler with the parameters
        Object result = null;
        try {
            result = inchHandler.getMethod().invoke(inchHandler.getController(),paramValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        //if the invoke is not working
        if(result == null) { return null; }
        //check the return type of the handler
        boolean isModeAndView = inchHandler.getMethod().getReturnType() == InchModelAndView.class;
        if(isModeAndView) {
            return (InchModelAndView) result;
        }else {
            return null;
        }
    }

    /**
     * convert the string into type of specified class
     * @param str the string
     * @param clazz the specified class
     * @return the correct class of this string, if cannot be done will return null
     */
    private Object typeConvertor(String str, Class<?> clazz) {
        if(clazz == String.class) {
            return str;
        } else if(clazz == Integer.class) {
            return Integer.valueOf(str);
        } else if(clazz == int.class) {
            return Integer.valueOf(str);
        } else if(clazz == Long.class) {
            return Long.valueOf(str);
        }else {
            return null;
        }
    }

    /**
     * check if the handler class is InchHandlerMapping
     * @param handler the handler need to be checked
     * @return if it is InchHandlerMapping
     */
    public boolean supports(Object handler) {
        return (handler instanceof InchHandler);
    }



}
